/*
 * OpenParts
 * A dynamic-loading components framework for GWT
 * Copyright (C) 2011 Christophe Bouthier  [chris{AT}binary-gastronome{DOT}fr]
 *
 * This work is partially based on work I have done at INRIA (http://www.inria.fr) 
 * in the context of the Qualipso European Project (http://qualipso.org/),
 * The work done at INRIA is Copyright (C) 2006-2011 INRIA
 *
 * This work is distributed under the LGPL version 3
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 3
 * as published by the Free Software Foundation. See the GNU
 * Lesser General Public License in LGPL.txt for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package fr.openparts.OpenParts.client;

import java.util.HashSet;
import java.util.Set;

import com.google.gwt.user.client.ui.UIObject;

/**
 * OPViewProxy class represent, in a container slot, the embedded view. It allows, through messages dispatching to the real embedded view, communication with
 * the embedded view. It also allows some manipulation ov the embedded view as if it was a local widget through proxy commands as defined in
 * {@link OPProxyCommands}.
 * 
 * @author Christophe Bouthier [chris{AT}binary-gastronome{DOT}fr]
 * @creation.date 18 January 2011
 */
public class OPViewProxy {

    /**
     * Slot id for this proxy
     */
    protected String slotId;

    /**
     * Set of all embedded view message handlers
     */
    protected Set<OPMessageFromEmbeddedViewHandler> handlers = new HashSet<OPMessageFromEmbeddedViewHandler>();

    /**
     * Constructor, taking the slot id as parameter.
     * 
     * @param slotId
     *            the id of this slot. Shouldn't be null. Shouldn't be empty.
     */
    public OPViewProxy(final String slotId) {
        super();

        assert slotId != null : "parameter slotId shouldn't be null";
        assert !slotId.isEmpty() : "parameter slotId shouldn't be empty";

        this.slotId = slotId;
    }

    /**
     * Add the given handler as handler of messages send by the embedded view represented by this proxy. If the given handler is already registered, doesn't do
     * anything. Returns <code>true</code> if the given handler is correctly registered, <code>false</code> otherwise (if it was already registered).
     * 
     * @param handler
     *            the handler to register. Shouldn't be null.
     * @return <code>true</code> if the given handler is correctly registered, <code>false</code> if it was already registered.
     */
    public boolean addMessageHandler(final OPMessageFromEmbeddedViewHandler handler) {
        assert handler != null : "parameter handler shouldn't be null";

        return handlers.add(handler);
    }

    /**
     * Remove the given handler as handler of messages send by the embedded view represented by this proxy. Doesn't do anything if the given handler wasn't
     * registered. Returns <code>true</code> if the given handler was registered, <code>false</code> otherwise.
     * 
     * @param handler
     *            the handler to unregister. Shouldn't be null.
     * @return <code>true</code> if the given handler was registered, <code>false</code> otherwise.
     */
    public boolean removeMessageHandler(final OPMessageFromEmbeddedViewHandler handler) {
        assert handler != null : "parameter handler shouldn't be null";

        return handlers.remove(handler);
    }

    /**
     * Dispatch the given message and its params to the embedded view.
     * <p>
     * The view will transfert it to every @link {@link OPMessageFromContainerSlotHandler} handlers that are registered on its @link {@link OPSlotProxy}.
     * 
     * @param message
     *            the message to dispatch. Shouldn't be null.
     * @param params
     *            the parameters of the message. Can be empty, can be null.
     */
    public void dispatchMessageToEmbeddedView(final String message, final OPParams params) {
        dispatchMessageToEmbeddedView(message, params, null);
    }

    /**
     * Dispatch the given message and its params to the embedded view.
     * <p>
     * The view will transfert it to every @link {@link OPMessageFromContainerSlotHandler} handlers that are registered on its @link {@link OPSlotProxy}.
     * <p>
     * The message should NOT be one already defined in @link {@link OPProxyCommands}, or else it will not be transfered to message handlers.
     * 
     * @param message
     *            the message to dispatch. Shouldn't be null.
     * @param params
     *            the parameters of the message. Can be empty, can be null.
     * @param callback
     *            the callback called at the end of the message dispatching. Can be null.
     */
    public void dispatchMessageToEmbeddedView(final String message, final OPParams params, final OPCommandCallback callback) {
        assert message != null : "parameter message shouldn't be null";

        final OPCommand command = OPCommandFactory.dispatchMessageToEmbeddedViewFactory(message, slotId, params);
        if (callback != null) {
            command.setCallback(callback);
        }

        OPShell.opBindingSharedInstance().scheduleCommand(command);
    }

    /**
     * Handle messages coming from the embedded view. Dispatch the message to all registered {@link OPMessageFromEmbeddedViewHandler}s. This method shouldn't be
     * called directly, it is called automatically by embedded view when {@link OPSlotProxy#dispatchMessageToContainerSlot(String, OPParams, OPCommandCallback)}
     * is called.
     * 
     * @param message
     *            the message to dispatch. Shouldn't be null.
     * @param params
     *            the parameters of the message. Can be empty, can be null.
     */
    void handleMessageFromEmbeddedView(final String message, final OPParamsJS paramsjs) {
        assert message != null : "parameter message shouldn't be null";

        OPParams params = null;
        if (paramsjs != null) {
            params = paramsjs.toOPParams();
        }

        for (OPMessageFromEmbeddedViewHandler handler : handlers) {
            handler.handleMessageFromEmbeddedView(message, params);
        }
    }

    /**
     * Check if there is a real view attached to this view proxy.
     * 
     * @return true if there is a real view attached to this proxy, false otherwise,
     */
    public boolean isProxyAttachedToRealView() {
        final OPMessagesDispatcher dispatcher = OPMessagesDispatcher.getDispatcherAttachedToId(slotId);
        if (dispatcher == null) {
            return false;
        }
        return dispatcher.isMessageFromContainerSlotHandlerCallbackRegistered();
    }

    /**
     * Proxy command : call the setStyleName method on the embedded view. Don't do anything if there is no view attached. Clears all style names of the embedded
     * view and sets it to the given style.
     * 
     * @see UIObject#setStyleName(String)
     * 
     * @param style
     *            the style name to set. Shouldn't be null.
     */
    public void setStyleName(final String style) {
        assert style != null : "parameter style shouldn't be null";

        if (isProxyAttachedToRealView()) {
            dispatchMessageToEmbeddedView(OPProxyCommands.OP_PROXY_COMMAND_SET_STYLE_NAME, new OPParams(OPProxyCommands.OP_PROXY_PARAM_VALUE, style));
        }
    }

    /**
     * Proxy command : call the addStyleName method on the embedded view. Don't do anything if there is no view attached. Adds a style name to the embedded
     * view.
     * 
     * @see UIObject#addStyleName(String)
     * 
     * @param style
     *            the style name to add. Shouldn't be null.
     */
    public void addStyleName(final String style) {
        assert style != null : "parameter style shouldn't be null";

        if (isProxyAttachedToRealView()) {
            dispatchMessageToEmbeddedView(OPProxyCommands.OP_PROXY_COMMAND_ADD_STYLE_NAME, new OPParams(OPProxyCommands.OP_PROXY_PARAM_VALUE, style));
        }
    }

    /**
     * Proxy command : call the removeStyleName method on the embedded view. Don't do anything if there is no view attached. Removes a style name.
     * 
     * @see UIObject#removeStyleName(String)
     * 
     * @param style
     *            the style name to remove. Shouldn't be null.
     */
    public void removeStyleName(final String style) {
        assert style != null : "parameter style shouldn't be null";

        if (isProxyAttachedToRealView()) {
            dispatchMessageToEmbeddedView(OPProxyCommands.OP_PROXY_COMMAND_REMOVE_STYLE_NAME, new OPParams(OPProxyCommands.OP_PROXY_PARAM_VALUE, style));
        }
    }

    /**
     * Proxy command : call the setStylePrimaryName method on the embedded view. Don't do anything if there is no view attached. Sets the embedded view primary
     * style name and updates all dependent style names.
     * 
     * @see UIObject#setStylePrimaryName(String)
     * 
     * @param style
     *            the style name to set. Shouldn't be null.
     */
    public void setStylePrimaryName(final String style) {
        assert style != null : "parameter style shouldn't be null";

        if (isProxyAttachedToRealView()) {
            dispatchMessageToEmbeddedView(OPProxyCommands.OP_PROXY_COMMAND_SET_STYLE_PRIMARY_NAME, new OPParams(OPProxyCommands.OP_PROXY_PARAM_VALUE, style));
        }
    }

    /**
     * Proxy command : call the addStyleDependentName method on the embedded view. Don't do anything if there is no view attached. Adds a dependent style name
     * by specifying the style name's suffix.
     * 
     * @see UIObject#addStyleDependentName(String)
     * 
     * @param styleSuffix
     *            the suffix to add to the style name. Shouldn't be null.
     */
    public void addStyleDependentName(final String styleSuffix) {
        assert styleSuffix != null : "parameter styleSuffix shouldn't be null";

        if (isProxyAttachedToRealView()) {
            dispatchMessageToEmbeddedView(OPProxyCommands.OP_PROXY_COMMAND_ADD_STYLE_DEPENDENT_NAME, new OPParams(OPProxyCommands.OP_PROXY_PARAM_VALUE,
                    styleSuffix));
        }
    }

    /**
     * Proxy command : call the removeStyleName method on the embedded view. Don't do anything if there is no view attached. Removes a dependent style name by
     * specifying the style name's suffix.
     * 
     * @see UIObject#removeStyleName(String)
     * 
     * @param styleSuffix
     *            the suffix to reomve of the style name. Shouldn't be null.
     */
    public void removeStyleDependentName(final String styleSuffix) {
        assert styleSuffix != null : "parameter styleSuffix shouldn't be null";

        if (isProxyAttachedToRealView()) {
            dispatchMessageToEmbeddedView(OPProxyCommands.OP_PROXY_COMMAND_REMOVE_STYLE_DEPENDENT_NAME, new OPParams(OPProxyCommands.OP_PROXY_PARAM_VALUE,
                    styleSuffix));
        }
    }

    /**
     * Proxy command: call the setWidth method on the embedded view. Don't do anything if there is no view attached. Sets the embedded view width.
     * 
     * @see UIObject#setWidth(String)
     * 
     * @param width
     *            the embedded view new width, in CSS format ("10px" for example)
     */
    public void setWidth(final String width) {
        assert width != null : "parameter width shouldn't be null";

        if (isProxyAttachedToRealView()) {
            dispatchMessageToEmbeddedView(OPProxyCommands.OP_PROXY_COMMAND_SET_WIDTH, new OPParams(OPProxyCommands.OP_PROXY_PARAM_VALUE, width));
        }
    }

    /**
     * Proxy command: call the setHeight method on the embedded view. Don't do anything if there is no view attached. Sets the embedded view height.
     * 
     * @see UIObject#setHeight(String)
     * 
     * @param height
     *            the embedded view new height, in CSS format ("10px" for example)
     */
    public void setHeight(final String height) {
        assert height != null : "parameter height shouldn't be null";

        if (isProxyAttachedToRealView()) {
            dispatchMessageToEmbeddedView(OPProxyCommands.OP_PROXY_COMMAND_SET_HEIGHT, new OPParams(OPProxyCommands.OP_PROXY_PARAM_VALUE, height));
        }
    }

    /**
     * Proxy command: call the setSize method on the embedded view. Don't do anything if there is no view attached. Sets the embedded view size (width and
     * height).
     * 
     * @see UIObject#setSize(String, String)
     * 
     * @param width
     *            the embedded view new width, in CSS format ("10px" for example)
     * @param height
     *            the embedded view new height, in CSS format ("10px" for example)
     */
    public void setSize(final String width, final String height) {
        assert width != null : "parameter width shouldn't be null";
        assert height != null : "parameter height shouldn't be null";

        if (isProxyAttachedToRealView()) {
            dispatchMessageToEmbeddedView(OPProxyCommands.OP_PROXY_COMMAND_SET_SIZE, new OPParams(OPProxyCommands.OP_PROXY_PARAM_VALUE_1, width,
                    OPProxyCommands.OP_PROXY_PARAM_VALUE_2, height));
        }
    }

    /**
     * Proxy command: call the setVisible method on the embedded view. Don't do anything if there is no view attached. Sets whether the embedded view is
     * visible.
     * 
     * @see UIObject#setVisible(boolean)
     * 
     * @param visible
     *            true to set the view visible, false otherwise.
     */
    public void setVisible(final boolean visible) {
        if (isProxyAttachedToRealView()) {
            final String value = (visible ? OPProxyCommands.OP_PROXY_VALUE_TRUE : OPProxyCommands.OP_PROXY_VALUE_FALSE);
            dispatchMessageToEmbeddedView(OPProxyCommands.OP_PROXY_COMMAND_SET_VISIBLE, new OPParams(OPProxyCommands.OP_PROXY_PARAM_VALUE, value));
        }
    }

    /**
     * Called when the embedded view is really attached to its container view. Call the {@link OPMessageFromEmbeddedViewHandler#onEmbeddedViewAttach()} method
     * on all registered {@link OPMessageFromEmbeddedViewHandler}s. This method shouldn't be called directly, it is called automatically when the view is
     * attached to the slot following a call to displayView.
     */
    public void onEmbeddedViewAttach() {
        for (OPMessageFromEmbeddedViewHandler hanlder : handlers) {
            hanlder.onEmbeddedViewAttach();
        }
    }

}
